package com.sunmouse.okhttplib;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;

import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;

import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Created by Kvin on 2015/12/26.
 */
public class JsonUtils {
    //judge whether an url is available
    private boolean isAvailable;
    //    private final MediaType MEDIA_TYPE = MediaType.parse("image/png;charset=utf-8");
    private final MediaType MEDIA_TYPE = MediaType.parse("application/octet-stream;charset=utf-8");
    private final MediaType MEDIA_TYPE_STRING = MediaType.parse("text/plain;charset=utf-8");
    private final MediaType MEDIA_TYPE_MARKDOWN = MediaType.parse("text/x-markdown; charset=utf-8");
    private final MediaType MEDIA_TYPE_JPG = MediaType.parse("image/jpg;charset=utf-8");

    private final static int CONNECT_TIMEOUT = 10;//unit second
    private final static int READ_TIMEOUT = 10;//unit second

    private JsonHttpResponse mJsonHttpResponse;
    private StringHttpResponse mStringHttpResponse;
    private String flag;//use for differing various requests
    private OkHttpClient client;
    private long connectTimeOut;
    private long readTimeOut;
    private Context context;

    private static JsonUtils utils;

    //request complete then call cancel
    private Call call;

    private boolean enabled;


    //use constructor to create new Object when should handling many requests
    private JsonUtils(long connectTimeOut, long readTimeOut) {
        initClient(connectTimeOut, readTimeOut);
    }

    public JsonUtils(Context context) {
        this(CONNECT_TIMEOUT, READ_TIMEOUT);
        this.context = context;
    }

    private void initClient(long connectTimeOut, long readTimeOut) {
        //set UI here
        client = new OkHttpClient();
        client.setConnectTimeout(connectTimeOut < 1 ? CONNECT_TIMEOUT : connectTimeOut, TimeUnit.SECONDS);//set timeout
        client.setReadTimeout(readTimeOut < 1 ? READ_TIMEOUT : readTimeOut, TimeUnit.SECONDS);
        this.enabled = true;
    }

    //create an instance
    public static JsonUtils newInstance(long connectTimeOut, long readTimeOut) {
        utils = new JsonUtils(connectTimeOut, readTimeOut);
        return utils;
    }

    public static JsonUtils newInstance() {
        return newInstance(CONNECT_TIMEOUT, READ_TIMEOUT);
    }

    //single instance
    public static JsonUtils getInstance(long connectTimeOut, long readTimeOut) {
        if (utils == null) {
            newInstance(connectTimeOut, readTimeOut);
        }
        return utils;
    }

    public static JsonUtils getInstance() {
        return getInstance(CONNECT_TIMEOUT, READ_TIMEOUT);
    }


    /**
     * request method:GET   return a json data
     *
     * @param path
     * @param tag  use for canceling requests
     * @param ck
     * @param flag use for differing various requests
     */
    public synchronized void get(String path, String tag, JsonHttpResponse ck, String flag) {
        mHandler.sendEmptyMessage(HttpCons.Tag.JSON_PRE_EXECUTE);
        if (!enabled) return;
        this.mJsonHttpResponse = ck;
        this.flag = flag;
        final Request request = new Request.Builder()
                .url(path)
                .tag(tag)
                .build();
        try {
            call = client.newCall(request);
            call.enqueue(new JsonCallback());
        } catch (Exception e) {
            final Message msg = Message.obtain();
            msg.what = HttpCons.Tag.JSON_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }
    }

    public synchronized void get(String path, JsonHttpResponse ck) {
        this.get(path, null, ck, HttpCons.DEFAULT_FLAG);
    }

    /**
     * request method:GET   return a string data
     *
     * @param path
     * @param tag  use for canceling requests
     * @param ck
     * @param flag use for differing various requests
     */
    public synchronized void get(String path, String tag, StringHttpResponse ck, String flag) {
        mHandler.sendEmptyMessage(HttpCons.Tag.STRING_PRE_EXECUTE);
        if (!enabled) return;
        this.mStringHttpResponse = ck;
        this.flag = flag;
        final Request request = new Request.Builder()
                .url(path)
                .tag(tag)
                .build();
        try {
            call = client.newCall(request);
            call.enqueue(new StringCallback());
        } catch (Exception e) {
            Message msg = Message.obtain();
            msg.what = HttpCons.Tag.STRING_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }
    }

    public synchronized void get(String path, String tag, StringHttpResponse ck) {
        this.get(path, tag, ck, HttpCons.DEFAULT_FLAG);
    }


    /**
     * request method:POST   return a json data
     *
     * @param path
     * @param params request params
     * @param ck
     * @param flag   use for differing various requests
     */
    public synchronized void post(String path, Map<String, String> params, JsonHttpResponse ck, String flag) {
        mHandler.sendEmptyMessage(HttpCons.Tag.JSON_PRE_EXECUTE);
        if (!enabled) return;
        this.mJsonHttpResponse = ck;
        this.flag = flag;
        FormEncodingBuilder builder = new FormEncodingBuilder();
        if (params != null) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                builder.add(entry.getKey(), entry.getValue() == null ? "" : entry.getValue());
            }
        }
        final Request request = new Request.Builder()
                .url(path)
                .post(builder.build())
                .build();
        try {
            call = client.newCall(request);
            call.enqueue(new JsonCallback());
        } catch (Exception e) {
            final Message msg = Message.obtain();
            msg.what = HttpCons.Tag.JSON_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }
    }

    public synchronized void post(String path, Map<String, String> params, JsonHttpResponse ck) {
        this.post(path, params, ck, HttpCons.DEFAULT_FLAG);
    }

    /**
     * request method:POST   return a string data
     *
     * @param path
     * @param params request params
     * @param ck
     * @param flag   use for differing various requests
     */
    public synchronized void post(String path, Map<String, String> params, StringHttpResponse ck, String flag) {
        mHandler.sendEmptyMessage(HttpCons.Tag.STRING_PRE_EXECUTE);
        if (!enabled) return;
        this.mStringHttpResponse = ck;
        this.flag = flag;
        FormEncodingBuilder builder = new FormEncodingBuilder();
        if (params != null) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                builder.add(entry.getKey(), entry.getValue() == null ? "" : entry.getValue());
            }
        }
        final Request request = new Request.Builder()
                .url(path)
                .post(builder.build())
                .build();
        try {
            call = client.newCall(request);
            call.enqueue(new StringCallback());
        } catch (Exception e) {
            Message msg = Message.obtain();
            msg.what = HttpCons.Tag.STRING_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }
    }

    public synchronized void post(String path, Map<String, String> params, StringHttpResponse ck) {
        this.post(path, params, ck, HttpCons.DEFAULT_FLAG);
    }

    /**
     * upload image or files
     *
     * @param path
     * @param params
     * @param key
     * @param files
     * @param bitmap
     * @param ck
     */
    public synchronized void uploadFile(String path, HashMap<String, String> params, String key, File[] files, Bitmap bitmap, JsonHttpResponse ck) {
        mHandler.sendEmptyMessage(HttpCons.Tag.JSON_PRE_EXECUTE);
        if (!enabled) return;
        this.mJsonHttpResponse = ck;
        OkHttpClient client = new OkHttpClient();
        MultipartBuilder multipartBuilder = new MultipartBuilder().type(MultipartBuilder.FORM);
        for (Map.Entry<String, String> p : params.entrySet()) {
            multipartBuilder.addFormDataPart(p.getKey(), p.getValue());
        }
        if (bitmap != null) {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
                baos.flush();
                multipartBuilder.addFormDataPart(key, "default", RequestBody.create(MEDIA_TYPE, baos.toByteArray()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (files != null && files.length > 0) {
            for (File file : files) {
                multipartBuilder.addFormDataPart(key, file.getName(), RequestBody.create(MEDIA_TYPE, file));
            }
        }
        final Request request = new Request.Builder()
                .url(path)
                .post(multipartBuilder.build())
                .build();
        try {
            call = client.newCall(request);
            call.enqueue(new JsonCallback());
        } catch (Exception e) {
            final Message msg = Message.obtain();
            msg.what = HttpCons.Tag.JSON_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }
    }

    //upload a set of files
    public synchronized void uploadFile(String path, HashMap<String, String> params, String key, File[] files, JsonHttpResponse ck) {
        this.uploadFile(path, params, key, files, null, ck);
    }

    //upload a single picture
    public synchronized void uploadFile(String path, HashMap<String, String> params, String key, File file, JsonHttpResponse ck) {
        mHandler.sendEmptyMessage(HttpCons.Tag.JSON_PRE_EXECUTE);
        if (!enabled) return;
        mJsonHttpResponse = ck;
        OkHttpClient client = new OkHttpClient();
        MultipartBuilder multipartBuilder = new MultipartBuilder().type(MultipartBuilder.FORM);
        if (params == null) return;
//        for (Map.Entry<String, String> param : params.entrySet()) {
//            multipartBuilder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + param.getKey() + "\""),
//                    RequestBody.create(null, param.getValue()));
//        }
        for (Map.Entry<String, String> p : params.entrySet()) {
            multipartBuilder.addFormDataPart(p.getKey(), p.getValue());
        }
        if (file == null) return;
//        multipartBuilder.addPart(Headers.of("Content-Disposition",
//                "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""),
//                RequestBody.create(MEDIA_TYPE, file));
        multipartBuilder.addFormDataPart(key, file.getName(), RequestBody.create(MEDIA_TYPE_JPG, file));
        final Request request = new Request.Builder()
                .url(path)
                .post(multipartBuilder.build())
                .build();
        try {
            call = client.newCall(request);
            call.enqueue(new JsonCallback());
        } catch (Exception e) {
            final Message msg = Message.obtain();
            msg.what = HttpCons.Tag.JSON_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }
    }

    public synchronized void uploadFile(String path, HashMap<String, String> params, String key, Bitmap bitmap, JsonHttpResponse ck) {
        this.uploadFile(path, params, key, null, bitmap, ck);
    }

    //cancel requests without being executed
    public void requestCancel(String tag) {
        if (client != null)
            client.cancel(tag);
    }


    /**
     * open or close accessible permission
     *
     * @param enabled
     */
    public void setEnabled(boolean enabled) {

        this.enabled = enabled;
    }

    //handle response data
    private final Handler mHandler = new HttpHandler();

    /**
     * http handler
     */
    private class HttpHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case HttpCons.Tag.JSON_SUCCESS://request success
                    if (mJsonHttpResponse == null) break;
                    mJsonHttpResponse.onSuccess((JSONObject) msg.obj, flag);
                    break;
                case HttpCons.Tag.JSON_FAILURE://request failure
                    if (mJsonHttpResponse == null) break;
                    mJsonHttpResponse.onFailure((Exception) msg.obj, flag);
                    break;
                case HttpCons.Tag.JSON_PRE_EXECUTE:
                    if (mJsonHttpResponse == null) break;
                    mJsonHttpResponse.onPreExecute(flag);
                    break;
                case HttpCons.Tag.STRING_SUCCESS:
                    if (mStringHttpResponse == null) break;
                    mStringHttpResponse.onSuccess((String) msg.obj, flag);
                    break;
                case HttpCons.Tag.STRING_PRE_EXECUTE:
                    if (mStringHttpResponse == null) break;
                    mStringHttpResponse.onPreExecute(flag);
                    break;
                case HttpCons.Tag.STRING_FAILURE:
                    if (mStringHttpResponse == null) break;
                    mStringHttpResponse.onFailure((Exception) msg.obj, flag);
                    break;
            }
        }
    }

    /**
     * String ck
     */
    private class StringCallback implements Callback {
        @Override
        public void onFailure(Request request, IOException e) {
            Message msg = Message.obtain();
            msg.what = HttpCons.Tag.STRING_FAILURE;
            msg.obj = e;
            mHandler.sendMessage(msg);
        }

        @Override
        public void onResponse(Response response) throws IOException {//execute in childThread
            Message msg = Message.obtain();
            msg.what = HttpCons.Tag.STRING_SUCCESS;
            String str = response.body().string();
            msg.obj = str;
            mHandler.sendMessage(msg);
        }

    }

    /**
     * Json ck
     */
    private class JsonCallback implements Callback {
        @Override
        public void onFailure(Request request, IOException e) {
            try {
                final Message msg = Message.obtain();
                msg.what = HttpCons.Tag.JSON_FAILURE;
                msg.obj = e;
                mHandler.sendMessage(msg);
            } catch (Exception ee) {
                e.printStackTrace();
            }
        }

        @Override
        public void onResponse(Response response) throws IOException {//execute in childThread
            try {
                Message msg = Message.obtain();
                msg.what = HttpCons.Tag.JSON_SUCCESS;
                String str = response.body().string();
//                if (StringUtils.isJSONObject(str)) {
//                    msg.obj = StringUtils.getJSONObject();
//                }
                mHandler.sendMessage(msg);
            } catch (Exception ee) {
               ee.printStackTrace();
            }
        }
    }

}
